package com.caishi.lkx.common.fliter;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.AES;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.caishi.lkx.user.model.*;
import com.caishi.lkx.user.service.IGroupService;
import com.caishi.lkx.user.service.ILoginInfoService;
import com.caishi.lkx.user.service.IMemberService;
import com.zzw.common.IUserContext;
import com.zzw.common.exception.BizException;
import com.zzw.common.filter.BaseMessageFilter;
import com.zzw.common.util.HttpWebUtil;
import com.zzw.common.util.SpringMVCUtil;
import com.zzw.common.utils.MD5Util;
import com.zzw.common.utils.TimeUtil;
import com.caishi.lkx.common.AclResultCode;
import com.caishi.lkx.common.Jwt;
import com.caishi.lkx.common.context.TokenData;
import com.caishi.lkx.common.context.UserContext;
import com.caishi.lkx.common.ienum.ClientType;
import com.caishi.lkx.common.ienum.DeviceType;
import com.caishi.lkx.common.ienum.PlatformType;
import com.caishi.lkx.common.util.HttpContextUtil;
import com.caishi.lkx.user.ienum.type.UserAccountType;
import com.caishi.lkx.user.ienum.type.UserChannelType;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Pattern;

/**
 * @author by keray
 * date:2021/4/1 10:49 上午
 */




@Slf4j
@Configuration
@Order(Integer.MIN_VALUE + 100)
@ConditionalOnBean(value = IUserContext.class)
@ConfigurationProperties(prefix = "dev.token")
public class TokenFilter implements Filter {

    private static final String TOKEN = "yuntk-auth-token";
    public static final String ADMIN_TOKEN = "yuntk-auth-token-admin";
    public static final String SOCKET_TOKEN_HEADER = "Sec-WebSocket-Protocol";
    // 用户id
    public static final String TOKEN_USER_ID_KEY = "uid";
    // 接口角色值
    public static final String TOKEN_ROLE_KEY = "role";
    // token生成时间
    public static final String TOKEN_TIME_KEY = "time";
    // 用户登录时登录类型
    public static final String TOKEN_USER_TYPE_KEY = "utp";
    // token版本
    public static final String TOKEN_USER_VERSION = "uv";
    // 用户登录时机构id
    public static final String TOKEN_USER_GROUP = "gId";
    // max(用户绑定机构到期时间,机构过期时间)
    public static final String TOKEN_USER_GROUP_TIME = "gidTime";

    // 用户登录方式id
    public static final String LOGIN_INFO_ID = "infoId";

    //token过期天数
    public static final int TOKEN_EXPIRE_DAYS = 30;

    //public static final String ZT_USER_KEY = "userKey";
    //sso共享用户信息
    public static final String OSS_USER_INFO = "ossUserInfo";
    public static final String OSS_DOMAIN = "cqrspx";
    public static final String ZT_ENCODE = "ZT.IhNNa1E8Fq5XtK941";
    public static final String PLATFORM_VERS7_ADMIN = "yuntk";
    public static final String PLATFORM_VERS7_PC = "student";
    public static final String PLATFORM_KAOYAN_PC = "PCkaoyan";
    public static final String PLATFORM_KAOYAN_ADMIN = "kaoyan";
    public static final String PLATFORM_CQRSPX_PC = "CQRSPXPC";

    @Getter
    @Setter
    private Boolean open = true;

    @Getter
    @Setter
    private String mockUserId;

    @Getter
    @Setter
    private String domain;
    @Getter
    @Setter
    private String cqrsmain;

    @Resource
    private UserContext userContext;

    @Autowired(required = false)
    private ILoginInfoService loginInfoService;

    @Autowired(required = false)
    private IMemberService memberService;

    @Autowired(required = false)
    private IGroupService groupService;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (request instanceof HttpServletRequest httpServletRequest) {
            var tokenData = userContext.currentTokenData();
            tokenData.setDevice(HttpContextUtil.currentDeviceType(httpServletRequest));
            var clientType = httpServletRequest.getHeader("clientType");
            tokenData.setClientType(StrUtil.isNotBlank(clientType) ? ClientType.valueOf(clientType) : ClientType.other);
            // 设置UUID
            String uuid = userContext.getDuid();
            String token = getToken(httpServletRequest);
            JSONObject tokenResult;
            log.info("获取登陆信息token:{}", token);
            if (StrUtil.isNotBlank(token)) {
                try {
                    tokenResult = new JSONObject();
                    tokenResult.putAll(Jwt.validToken(token));
                    // 校验token是否过期
                    if (tokenExpireCheck(tokenResult)) {
                        //
                        SpringMVCUtil.responseWriteWith((HttpServletResponse) response, AclResultCode.tokenExpire);
                        return;
                    }
                    try {
                        var gid = tokenResult.getString(TOKEN_USER_GROUP);
                        var groupTime = tokenResult.getString(TOKEN_USER_GROUP_TIME);
                        BeanUtil.copyProperties(TokenData.builder()
                                .userId(tokenResult.getString(TOKEN_USER_ID_KEY))
                                .userRole(Arrays.asList(tokenResult.getString(TOKEN_ROLE_KEY).split(",")))
                                .time(Long.parseLong(tokenResult.getString(TOKEN_TIME_KEY)))
                                .deviceUUID(tokenResult.getString(BaseMessageFilter.TOKEN_DEVICE_UUID_KEY))
                                .userTypeName(tokenResult.getString(TOKEN_USER_TYPE_KEY))
                                .version(tokenResult.getString(TOKEN_USER_VERSION))
                                .infoId(tokenResult.getString(LOGIN_INFO_ID))
                                .groupId(gid == null ? null : Long.parseLong(gid))
                                .groupTime(ObjectUtil.isNotEmpty(groupTime) ? LocalDateTime.parse(groupTime, TimeUtil.DATE_TIME_FORMATTER_SC) : null)
                                .build(), tokenData, CopyOptions.create().setIgnoreNullValue(true));

                        if (!tokenCheck(tokenData,token, (HttpServletResponse) response)) {
//                            为什么这里要返回呢。获取不到验证码
                            return;
                        }
                    } catch (Exception e) {
                        log.error("token 解析失败", e);
                        SpringMVCUtil.responseWriteWith((HttpServletResponse) response, AclResultCode.tokenExpire);
                        return;
                    }
//                    if (!uuid.equals(userContext.currentTokenData().getDeviceUUID())) {
//                        log.error("原UUID：{},先UUID：{}", userContext.currentTokenData().getDeviceUUID(), uuid);
//                        SpringMVCUtil.responseWriteWith((HttpServletResponse) response, AclResultCode.tokenExpire);
//                        return;
//                    }
                } catch (Exception ignored) {
//                    System.out.println("token 解析失败");
                }
            }
            tokenData.setDeviceUUID(uuid);
            tokenData.setPlatformType(getPlatformType(httpServletRequest));
        }
        chain.doFilter(request, response);
    }

    private boolean tokenExpireCheck(Map<String, Object> tokenResult) {

        if (tokenResult.get(TOKEN_TIME_KEY) != null) {
            var time = Long.parseLong(tokenResult.get(TOKEN_TIME_KEY).toString());
            if (System.currentTimeMillis() - time > (long) TOKEN_EXPIRE_DAYS * 24 * 60 * 60 * 1000) {
                return true;
            }
        }

        return false;
    }

    private boolean tokenCheck(TokenData tokenData,String token, HttpServletResponse response) throws IOException {
        if (tokenData.getGroupTime() != null && tokenData.getGroupTime().isBefore(LocalDateTime.now())) {
            response.addIntHeader("groupTimeExpire", 1);
        }
        var oldToken = redisTemplate.opsForValue().get(TokenFilter.userTokeKey(tokenData.getUserId(),tokenData.getDevice().name()));
        if(!token.equals(oldToken)){
            SpringMVCUtil.responseWriteWith((HttpServletResponse) response, AclResultCode.tokenExpire);
            return false;
        }
        return true;
    }

    private PlatformType getPlatformType(HttpServletRequest request) {
        String platform = request.getHeader("platform");
        if (StrUtil.isNotBlank(platform) && (platform.equals(PLATFORM_KAOYAN_PC) || platform.equals(PLATFORM_KAOYAN_ADMIN))) {
            return PlatformType.KAOYAN;
        } else {
            return PlatformType.VERS7;
        }

    }

    private String getToken(HttpServletRequest request) {
        DeviceType deviceType = HttpContextUtil.currentDeviceType(request);
        String tokenKey = TOKEN;
        if (deviceType == DeviceType.admin) {
            tokenKey = ADMIN_TOKEN;
        }
        Function<String, String> fun = tokenStr -> {
            String token = request.getHeader(tokenStr);
            if (token != null) {
                return token;
            }
            Cookie[] cookies = request.getCookies();
            if (cookies != null && cookies.length > 0) {
                token = HttpWebUtil.getCookieValue(request, tokenStr);
                if (StrUtil.isNotBlank(token)) {
                    return token;
                }
            }
            String str = request.getHeader(SOCKET_TOKEN_HEADER);
            if (StrUtil.isNotBlank(str)) {
                String[] vales = str.split(",");
                if (vales.length > 0) {
                    return vales[1].trim();
                }
            }
            return null;
        };
        String token = fun.apply(tokenKey);
        // 兼容老版本的tokenKey
        if (StrUtil.isBlank(token)) {
            token = fun.apply("vip-auth-token");
        }
        return token;
    }

    public void responseSetToken(DeviceType deviceType, HttpServletResponse response, String token, Boolean temporary) {
        String tokenStr = TOKEN;
        if (deviceType == DeviceType.admin) {
            tokenStr = ADMIN_TOKEN;
        }
        Cookie cookie = new Cookie(tokenStr, token);
        //是否生成临时token
        if (temporary) {
            cookie.setMaxAge(-1);

        } else {
            cookie.setMaxAge(7 * 24 * 3600);
        }
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
       // String istest = request.getHeader("test");
       // if ((StrUtil.isBlank(istest) || istest.equals("false")) && StrUtil.isNotBlank(domain))
        if(deviceType == DeviceType.cqrspxpc){
            cookie.setDomain(cqrsmain);
        }

        cookie.setPath("/");
        cookie.setHttpOnly(false);
        response.addCookie(cookie);

    }

    public static boolean isValidDomainName(String domainName) {
        Pattern pDomainNameOnly = Pattern.compile("^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,6}$");
        return pDomainNameOnly.matcher(domainName).find();
    }

    public void responseSetUserInfo(UserModel userModel, DeviceType deviceType, HttpServletResponse response) {
        if (deviceType == DeviceType.admin) {
            return;
        }
        MemberModel model = memberService.getById(userModel.getId());
        if (null == model) return;
        Long groupId = model.getGroupId();
        OssUserModel ossUserModel = new OssUserModel();
        if (null != groupId) {

            GroupModel groupModel = groupService.getById(groupId);
            if (null != groupModel) {
               // ossUserModel.setGroupUserId(groupModel.getId());
            }

        }
        ossUserModel.setMobile(userModel.getMobile());
        ossUserModel.setName(userModel.getName());
        ossUserModel.setExpireTime(model.getExpireTime() != null ? model.getExpireTime().toLocalDate() : null);
        ossUserModel.setSex(userModel.getSex());
        ossUserModel.setHeader(userModel.getHeader());
        ossUserModel.setCard(model.getCard());
        ossUserModel.setEmail(model.getEmail());
        ossUserModel.setCreateTime(model.getCreatedTime());
        ossUserModel.setDescription(model.getDescription());
        ossUserModel.setStuNo(model.getStuNo());
        ossUserModel.setType(UserChannelType.pcWeb7);
        LoginInfoModel loginInfoModel = loginInfoService.selectUserLoginInfo(userModel.getId(), UserAccountType.username);
        if (null != loginInfoModel) {
            ossUserModel.setUserName(loginInfoModel.getAccount());
            ossUserModel.setPassword(MD5Util.MD5Encode(loginInfoModel.getCertificate()));
        }
        String md5 = MD5Util.MD5Encode(OSS_USER_INFO + OSS_DOMAIN);
        var aes = new AES(Mode.ECB, Padding.ZeroPadding, md5.getBytes(StandardCharsets.UTF_8));
        String s = aes.encryptBase64(JSON.toJSONString(ossUserModel));
        Cookie cookie = new Cookie(OSS_USER_INFO, s);
        cookie.setMaxAge(7 * 24 * 3600);
        cookie.setPath("/");
        cookie.setDomain(OSS_DOMAIN);
        cookie.setHttpOnly(false);
        response.addCookie(cookie);
    }

    //        public static void main(String[] args) {
//            String s1 = OSS_USER_INFO + OSS_DOMAIN;
//            System.out.println(s1);
//            String md5 = MD5Util.MD5Encode(OSS_USER_INFO + OSS_DOMAIN);
//            System.out.println(md5);
//        var aes = new AES(Mode.ECB, Padding.ZeroPadding,md5.getBytes(StandardCharsets.UTF_8));
//        String s = aes.decryptStr("SP2GHFkDLPGJJlSNdGpjX8oNyNh0ZHS/tqpzytcqPIB06s7I+2M0OCA/BVtk9f8SBt6yRhZeVAjigvgekCvh3VDGlhMwSx86ZOk6jlPu0xyjUeEokoP6R+uYBMv64ENcGilOdJfIftOZB1lI9Ee/PIqS7bC39w8Xdor0rzBmz2ARQm6eiqYdzAPdsGIZl0ElTRSUNsT/v8KyswNQ3NsihB+eQIiISONBBDHXlp+aYyx93onzPyXY4ZlZ92WbVuR194Z3TcQLSPnaL1AS6Gx/zCbo7DqJFGRDQJTFEFMj5qIgQ93bv7MzBJM8/C1EmmWL");
//        System.out.println(s);
//    }
    public void responseRemoveToken(DeviceType deviceType, HttpServletResponse response) {
        String tokenStr = TOKEN;
        if (deviceType == DeviceType.admin) {
            tokenStr = ADMIN_TOKEN;
        } else {
            Cookie ossCookie = new Cookie(OSS_USER_INFO, null);
            ossCookie.setMaxAge(0);
            ossCookie.setPath(domain);
            if(deviceType == DeviceType.cqrspxpc){
                ossCookie.setDomain(cqrsmain);
            }

            ossCookie.setHttpOnly(false);
            response.addCookie(ossCookie);
        }
        Cookie cookie = new Cookie(tokenStr, null);
        cookie.setMaxAge(0);
        System.out.println("取消domain");
        if(deviceType == DeviceType.cqrspxpc){
            cookie.setDomain(cqrsmain);
        }
        cookie.setPath("/");
        cookie.setHttpOnly(false);
        response.addCookie(cookie);
    }

    public String resetToken(String userId, HttpServletRequest request) throws BizException {
        var token = getToken(request);
        var data = Jwt.validToken(token);
        data.put(TOKEN_USER_ID_KEY, userId);
        data.put(TOKEN_TIME_KEY, System.currentTimeMillis());
        return Jwt.createToken(data);
    }

    public static String userTokeKey(String userId,String deviceName){
      return  String.format("token:%s:%s", userId,deviceName);
    }

}
